iT邦幫忙

第 11 屆 iThome 鐵人賽

DAY 24
1
Modern Web

前端三十 - 成為更好的前端工程師系列 第 24

24. [BE] 請說明一下 npm 的套件管理機制。

  • 分享至 

  • xImage
  •  

package

在現代的網站中,使用他人開放原始碼的套件輔助開發已經是稀鬆平常的事情,無論是透過套件加速堆砌產品,或是在開發環境中加上協助工程師的各式工具,只需要稍加設定,一個專案便能輕易加載了成千上萬的外部程式;但如此方便的機制,究竟是怎麼實現的呢?

本系列文已經重新編校彙整編輯成冊,並正式出版囉!
《前端三十:從 HTML 到瀏覽器渲染的前端開發者必備心法》好評販售中!
喜歡我文章內容的讀者們,歡迎您 前往購買 支持!

套件管理工具

對前端開發者來說,最熟悉的應該是安裝 Node.js 時自動附帶的 npm 吧?npm 即為 Node Package Manager 的縮寫,開發者可以透過 Node 隨附的 npm cli,進行套件的安裝及管理。

例如在專案資料夾的終端機中輸入 npm install express,npm 便會自動從 Registry 中尋找 express 這個非常熱門的 Node.js Web Server 框架,取得最新版本,下載到專案中的 node_modules 資料夾中。

然而在專案中,不可能每次都透過開發者自行指定套件安裝,不但無法管理,也很沒有效率;開發者可以透過專案中的 package.json,羅列出專案需要哪些套件,之後安裝時只需要 npm install,npm 便會自動依照 package.json 的內容下載套件。

版本控制

由於套件本身也是由開發者撰寫,也需要持續的更新迭代,公開出來的套件往往不會只有一個版本,套件管理工具自然也要能紀錄並提供各版本的套件供開發者使用;在 npm 的設計中,要求套件開發者透過 Semantic Versioning 的規範來定義套件版本:

  • 主版號:當你做了 不相容 的 API 修改
  • 次版號:當你做了向下相容的 功能性新增
  • 修訂號:當你做了向下相容的 問題修正

而使用套件的開發者,則需透過指定的字元,在 package.json 中設定專案欲使用的套件版本,例如下列的例子:

"dendencies": {
    "accepts": "~1.3.7",
    "array-flatten": "1.1.1",
    "body-parser": "^1.19.0",
    // ...
  }
  • 1.1.1:指定為 1.1.1 的版本
  • ~1.3.7:指定為 >= 1.3.7 且 < 1.4.0 的版本
  • ^1.19.0:指定為 >= 1.19.0 且 < 2.0.0 的版本

除了以上較常用的設定外,更詳細的可以參考 這裡

Yarn

除了預設的 npm,另一個廣為人知的套件管理工具是由 Facebook 開源出來的 Yarn,會聲名大噪的原因,一方面是 Facebook 及眾多開源大神共同開發的品牌效應,另外也因為早期的 npm (< npm @5)在套件版本控制上,時常沒有正確的效果,而當時的 Yarn 就透過一個 yarn.lock 檔,鎖定所有依賴套件的版本,完美解決了這個問題;但現在版本的 npm 也效仿 Yarn 的作法,預設會產出 package-lock.json 來固定套件版本,當時的嚴重缺陷也早已被解決,整體來說,使用 Yarn 的帶來的便利性也已經沒有那麼大,就筆者目前的感受,是沒有太大明顯差異的。

套件管理在成熟專案的重要性早已不言而喻,除了前述的 npm 及 Yarn,一直以來也有許多開發者投身其中,鑽研出其他的套件管理工具;例如 Bowervolopnpm 等等,有興趣的讀者也可以個別深入研究,這邊就不詳述了。

Install 的執行過程

那麼來進入正題吧,在透過這些套件管理工具進行安裝時,背後的機制究竟是什麼呢?下面將執行 npm install 的過程拆成五個步驟,也許各家套件管理工具實作會略有不同,但不外乎都會經過這些階段:

1. 計算缺少的套件

npm 會從專案中的三隻檔案,計算出本次 npm install 需要重新下載安裝的內容:

  • 專案內的 node_modules 結構
  • 開發者設定的 package.json
  • npm install 後自動生成的 package-lock.json

由於 package.json 中的套件版本可能會是使用 Semantic Versioning 描述的,npm 需要以 package.json 描述的版本為基礎,與 node_modules 及 package-lock.json 相互比對後,才能計算出需要更新的套件。

2. 從 Registry 取得套件資訊

計算出來缺少套件列表後,npm 向指定的 Registry 獲取各目標套件的 package.json、查詢可用版本,並解析出下載 URL。

3. 計算差異

由於套件本身也是專案,也可能引用其他套件,不同的套件引用到相同的套件這種事,自然也是稀鬆平常;npm 在這步驟會去計算各套件的 package.json,整理各套件個別需要下載的版本(有可能同套件需要多版本)最後產出整個專案所使用的的套件結構樹(package-lock.json)。

4. 下載、提取真正需要的套件

有了前面這麼多步驟的整理,接著就開始依序下載套件,並將下載的內容解壓縮,提取到 node_modules 資料夾中;這個步驟是 npm install 需時最長、最耗效能的步驟,主要是因為 下載、解壓縮、寫入硬碟分別需要網路、CPU 及硬碟 IO 的支撐,只要硬體設備的其中一環資源較缺乏,開發者馬上就會有感,速度自然也就快不起來。

為了解決這個問題,如同我們 昨天聊的網頁快取,npm 本身也擁有本地快取機制,在寫入到 node_modules 時,同時會寫入一份到電腦的本地快取中,未來如果有其他專案需要用到同一個版本的套件,npm 會在向 Registry 確認版本未更新(ETag 相符,回傳 304)後,直接複製快取的套件到 node_modules。

5. 執行每個套件的 install。

檔案全部都寫入到 node_modules 後,npm 會執行所有套件的 npm install,讓套件本身的依賴被正確的連結到下載的套件上,並觸發各套件 install、postinstall 的 npm-scripts hook,最後完成 npm install 指令。

安全性問題

注意到了嗎?前述執行的過程中,即使在 npm 有快取的情況下,仍然不會使用同一份檔案,而是複製一份套件到 node_modules 中;也因為這樣的特性,加上套件依賴套件再依賴套件的層層相依,node_modules 非常容易莫名的塞好塞滿,長成誇張的容量黑洞。

https://ithelp.ithome.com.tw/upload/images/20191010/20111380t9EkQTYpmS.jpg

除此之外,層層相依同時也帶來了風險,開發者很容易在不知情中安裝了有風險,甚至含有惡意程式的套件,從去年的 getcookiesEvent-Stream,到前幾個月的 bb-builder,甚至是熱門的工具庫套件 lodash 先前都有資安風險,例子不勝枚舉。

開發者在使用套件時,還是要盡可能掌握清楚專案中所使用的套件,並透過 Semantic Versioning 指定套件版本,以及將 package-lock.json 放進版控,徹底將套件版本鎖住,以避免重要的專案莫名遭到池魚之殃。

套件管理的未來

npm 很方便,但也有著不少問題;npm 的前核心開發者 Kat MarchánJSConf EU 2019 正式公開了 Tink,號稱是下一代的套件管理工具,它的核心概念是利用 JavaScript 直譯的特性, 將套件管理的機制從編譯階段移到執行階段,也如同其他程式語言的套件管理方式,將全部專案的套件統一管理,讓專案中幾乎不再需要獨立的 node_modules 資料夾。

"/node_modules" is massive. And is also where dreams go to die" -- Kat Marchán, 2019.

看起來一切美好,加上 npm 也將此 Tink 排進了未來的 Roadmap 上;雖然目前還只在相對早期的開發狀態,但仍然非常值得開發者持續關注後續發展!

結語

現代前端開發透過 npm 安裝套件,並經過 打包工具 的處理,在 前端三大框架 的普及之下,都已經是基本功了;但如同本系列文一直以來所強調的,使用工具的同時,還是要理解為何而用,以及其背後的機制,才能夠真正的控制並活用工具。

那麼以上就是今天的內容啦,明天就繼續往前端必須知道的後端知識前進吧!

參考資料

筆者

Gary

半路出家網站工程師;半生熟的前端加上一點點的後端。
喜歡音樂,喜歡學習、分享,也喜歡當個遊戲宅。

相信一切安排都是最好的路。


上一篇
23. [FE] 網頁的快取機制是怎麼運作的?
下一篇
25. [BE] Node.js 與 JavaScript 的關係是什麼?
系列文
前端三十 - 成為更好的前端工程師31
圖片
  直播研討會
圖片
{{ item.channelVendor }} {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

1 則留言

1
DanSnow
iT邦好手 1 級 ‧ 2021-03-15 10:45:09

現在的 Yarn v2 又增加了一個新的特性叫 PnP (雖然之前 Yarn v1 時就有實驗性的支援了),跟原本的 node_modules 的安裝方式比起來,安裝的速度快非常多,同時安裝完的檔案也小很多,只是不是所有的套件都能被用這樣的方法安裝,因為它是透過改寫 require 的邏輯的方式徹底改變了套件的安裝與載入方式

我要留言

立即登入留言